/* -*-C++-*- */
/** \addtogroup irdefs
  * @{
  */
/*
   This file contains IR related to representating the type hierarchy.
   Some of these classes never appear in the IR tree, they are just
   synthesized by the type-checker.
*/

#emit
namespace IR {
enum class Direction {
    None,
    In,
    Out,
    InOut
};

inline cstring directionToString(IR::Direction direction) {
    switch (direction) {
        case IR::Direction::None:
            return "<none>";
        case IR::Direction::In:
            return "in";
        case IR::Direction::Out:
            return "out";
        case IR::Direction::InOut:
            return "inout";
        default:
            BUG("Unhandled case");
    }
}
}  // namespace IR

inline std::ostream& operator<<(std::ostream &out, IR::Direction d) {
    switch (d) {
        case IR::Direction::None:
            break;
        case IR::Direction::In:
            out << "in";
            break;
        case IR::Direction::Out:
            out << "out";
            break;
        case IR::Direction::InOut:
            out << "inout";
            break;
        default:
            BUG("Unhandled case");
    }
    return out;
}

inline bool operator>>(cstring s, IR::Direction &d) {
    if (!s || s == "") d = IR::Direction::None;
    else if (s == "in") d = IR::Direction::In;
    else if (s == "out") d = IR::Direction::Out;
    else if (s == "inout") d = IR::Direction::InOut;
    else return false;
    return true;
}
#end

/// Represents the type of a type.
/// For example, in a declaration like
/// bit<32> x;
/// The type of x is Type_Bits(32);
/// The type of 'bit<32>' is Type_Type(Type_Bits(32))
/// TypeType should not appear in the program IR, just in the TypeMap produced
/// by type-checking.
class Type_Type : Type {
    Type type;
    toString { return "Type(" + type->toString() + ")"; }
    dbprint { out << "Type(" << type << ")"; }
    Type getP4Type() const override { return type; }
    validate { BUG_CHECK(!type->is<IR::Type_Type>(), "%1%: nested Type_Type", type); }
}

class Type_Boolean : Type_Base {
    static Type_Boolean get();
    int width_bits() const override { return 1; }
    toString{ return "bool"; }
    dbprint { out << "bool"; }
}

/// The type of a parser state
class Type_State : Type_Base {
    static Type_State get();
    toString{ return "state"; }
    dbprint { out << "state"; }
}

/// Represents both bit<> and int<> types in P4-14 and P4-16
class Type_Bits : Type_Base {
    optional int size = 0;      // zero (only) for not-yet evaluated const expression
    NullOK optional Expression expression; // only used temporarily
    bool isSigned;
    static Type_Bits get(Util::SourceInfo si, int sz, bool isSigned = false);
    static Type_Bits get(int sz, bool isSigned = false);
    cstring baseName() const { return isSigned ? "int" : "bit"; }
    int width_bits() const override { return size; }

    toString{ return baseName() + "<" + Util::toString(size) + ">"; }
    dbprint { out << toString(); }
}

class Type_Varbits : Type_Base {
    optional int         size = 0;   // if zero it means "unknown"
    NullOK optional Expression  expression = nullptr;  // only used temporarily
    static Type_Varbits get(Util::SourceInfo si, int size = 0);
    static Type_Varbits get();
    toString{ return cstring("varbit<") + Util::toString(size) + ">"; }
    dbprint { out << "varbit<" << size << ">"; }
}

class Parameter : Declaration, IAnnotated {
    optional Annotations annotations = Annotations::empty;
    Direction           direction;
    Type                type;
    optional NullOK Expression defaultValue;
    Annotations getAnnotations() const override { return annotations; }
    bool hasOut() const
    { return direction == IR::Direction::Out || direction == IR::Direction::InOut; }
    bool isOptional() const {
        return getAnnotation(Annotation::optionalAnnotation) != nullptr; }
    dbprint { out << annotations << direction << (direction != IR::Direction::None ? " " : "")
                  << type << ' ' << name; }
}

class ParameterList : ISimpleNamespace {
    optional inline IndexedVector<Parameter> parameters;
    validate{ parameters.check_null(); }
    Util::Enumerator<Parameter>* getEnumerator() const {
        return parameters.getEnumerator(); }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return parameters.getDeclarations(); }
    size_t size() const { return parameters.size(); }
    bool empty() const { return size() == 0; }
    IR::Parameter getParameter(cstring name) const {
        return parameters.getDeclaration<Parameter>(name); }
    IR::Parameter getParameter(unsigned index) const {
        for (auto &param : parameters)
            if (0 == index--) return param;
        BUG("Only %1% parameters; index #%2% requested", size(), size()+index); }
    IR::IDeclaration getDeclByName(cstring name) const override { return getParameter(name); }
    void push_back(const Parameter *p) { parameters.push_back(p); }
    toString {
        cstring result = "";
        bool first = true;
        for (auto p : parameters) {
            if (!first)
                result += ", ";
            first = false;
            result += p->toString();
        }
        return result;
    }
#emit
    auto begin() const -> decltype(parameters.begin()) { return parameters.begin(); }
    auto end() const -> decltype(parameters.end()) { return parameters.end(); }
#end
}

/// Represents a type variable written by the user
class Type_Var : Type_Declaration, ITypeVar {
    cstring getVarName() const override { return getName(); }
    int getDeclId() const override { return declid; }
    dbprint { out << name << "/" << getDeclId(); }
    toString { return getName().toString(); }
}

/// Stands for the 'int' type: infinite precision constant
/// However, we represent it as a type variable, because we use
/// type unification to discover the correct type for the constants
/// in some contexts.
class Type_InfInt : Type, ITypeVar {
    int declid = nextId++;
 private:
    static int nextId;
 public:
    cstring getVarName() const override { return "int_" + Util::toString(declid); }
    int getDeclId() const override { return declid; }
    dbprint { out << "int/" << declid; }
    toString { return "int"; }
    operator== { return declid == a.declid; }
    equiv {
        (void)a;  // silence unused warning
        return true; /* ignore declid */
    }
    const Type* getP4Type() const override { return this; }
}

class Type_Dontcare : Type_Base {
    toString{ return "_"; }
    static Type_Dontcare get();
    dbprint { out << "_"; }
}

class Type_Void : Type_Base {
    toString{ return "void"; }
    static Type_Void get();
    dbprint { out << "void"; }
}

class Type_MatchKind : Type_Base {
    toString{ return "match_kind"; }
    static Type_MatchKind get();
    dbprint { out << "match_kind"; }
}

class TypeParameters : ISimpleNamespace {
    optional inline IndexedVector<Type_Var> parameters;
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return parameters.getDeclarations(); }
    bool empty() const { return parameters.empty(); }
    size_t size() const { return parameters.size(); }
    IR::IDeclaration getDeclByName(cstring name) const override {
        return parameters.getDeclaration(name); }
    void push_back(Type_Var tv) { parameters.push_back(tv); }
    validate{ parameters.check_null(); }
    toString {
        if (parameters.size() == 0)
            return "";
        cstring result = "<";
        bool first = true;
        for (auto p : parameters) {
            if (!first)
                result += ", ";
            first = false;
            result += p->toString();
        }
        result += ">";
        return result;
    }
}

class StructField : Declaration, IAnnotated {
    optional Annotations annotations = Annotations::empty;
    Type        type;
    Annotations getAnnotations() const override { return annotations; }
}

abstract Type_StructLike : Type_Declaration, INestedNamespace, ISimpleNamespace, IAnnotated, IMayBeGenericType {
    static const cstring minSizeInBits;
    static const cstring minSizeInBytes;
    optional Annotations annotations = Annotations::empty;
    optional TypeParameters typeParameters = new TypeParameters();
    optional inline IndexedVector<StructField>  fields;
    TypeParameters getTypeParameters() const override { return typeParameters; }
    std::vector<INamespace> getNestedNamespaces() const override { return { typeParameters }; }
    Annotations getAnnotations() const override { return annotations; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return fields.getDeclarations(); }
    StructField getField(cstring name) const {
        return fields.getDeclaration<StructField>(name); }
    int getFieldIndex(cstring name) const {
        int index_pos = 0;
        for (auto f : fields) {
            if (f->name == name)
                return index_pos;
	    index_pos++;
        }
        return -1;
    }
    /// This function returns start offset of the given field name in bits.
    /// If the given name is not a valid field name, -1 is returned.
    /// The given offset may not be correct if varbit field(s) present in between.
    /// Offset for all fields will be correct if:
    ///  - the type has only fixed width fields
    ///  - the type has fixed width fields with only one varbit field as a last member.
    int getFieldBitOffset(cstring name) const {
        int offset = 0;
        for (auto f : fields) {
            if (f->name == name) {
                return offset;
            }
            offset += f->type->width_bits();
        }
        return -1;
    }
    int width_bits() const override {
        int rv = 0;
        for (auto f : fields) {
            rv += f->type->width_bits();
        }
        return rv; }
    IR::IDeclaration getDeclByName(cstring name) const override {
        return fields.getDeclaration(name); }
    validate{ fields.check_null(); }
    dbprint;
#apply
}

class Type_Struct : Type_StructLike {
#nodbprint
    toString{ return cstring("struct ") + externalName(); }
}

/// This is the type of a struct-valued expression whose
/// exact struct type is yet unknown; we only know the field names
/// and some information about their types.
class Type_UnknownStruct : Type_StructLike {
#nodbprint
}

class Type_HeaderUnion : Type_StructLike {
#nodbprint
    toString{ return cstring("header_union ") + externalName(); }
    // this makes some assumptions on padding
    int width_bits() const override {
        int rv = 0;
        for (auto f : fields)
            rv = std::max(rv, f->type->width_bits());
        return rv; }
    /// start offset of any field in a union is 0
    int getFieldBitOffset(cstring name) const {
        for (auto f : fields) {
            if (f->name == name) {
                return 0;
            }
        }
        return -1; }
}

class Type_Header : Type_StructLike {
    static const cstring setValid;
    static const cstring setInvalid;
    static const cstring isValid;
#nodbprint
    toString{ return cstring("header ") + externalName(); }
}

class Type_Set : Type {
    Type elementType;
    dbprint{ Node::dbprint(out); out << "<" << elementType << ">"; }
    toString { return cstring("set<") + elementType->toString() + ">"; }
    const Type* getP4Type() const override { return nullptr; }
    int width_bits() const override {
        /// returning the width of the set elements, not the set itself, which doesn't
        /// really have a sensible size
        return elementType->width_bits(); }
}

interface Type_Indexed {
    // Probably this sohuld be called 'size()', but some subclasses already
    // have a 'size' field.
    virtual size_t getSize() const = 0;
    Type at(size_t index) const;
}

/// Base class for Type_List and Type_Tuple
abstract Type_BaseList : Type, Type_Indexed {
    optional inline Vector<Type> components;
    validate{ components.check_null(); }
    size_t getSize() const override { return components.size(); }
    Type at(size_t index) const { return components.at(index); }
    int width_bits() const override {
        /// returning sum of the width of the elements
        int rv = 0;
        for (auto f : components) {
            rv += f->width_bits();
        }
        return rv; }
    toString{
        std::string result = "tuple<";
        bool first = true;
        for (auto t : components) {
            if (!first)
                result += ", ";
            first = false;
            result += t->toString();
        }
        return result + ">";
    }
}

/// The type of an expressionList; can be unified with both Type_Tuple and Type_Struct
class Type_List : Type_BaseList {
    // In error messages this is shown as if it is a Tuple
    const Type* getP4Type() const override;
}

/// The type of a tuple.
class Type_Tuple : Type_BaseList {
    const Type* getP4Type() const override;
}

/// The type of an architectural block.
/// Abstract base for Type_Control, Type_Parser and Type_Package
abstract Type_ArchBlock : Type_Declaration, IMayBeGenericType, IAnnotated, ISimpleNamespace {
    optional Annotations annotations = Annotations::empty;
    optional TypeParameters typeParameters = new TypeParameters;
    Annotations getAnnotations() const override { return annotations; }
    TypeParameters getTypeParameters() const override { return typeParameters; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return typeParameters->getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return typeParameters->getDeclByName(name); }
}

class Type_Package : Type_ArchBlock, IContainer, ISimpleNamespace {
    ParameterList constructorParams;
    Type_Method getConstructorMethodType() const override;
    ParameterList getConstructorParameters() const override { return constructorParams; }
    toString{ return cstring("package ") + externalName(); }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return typeParameters->getDeclarations()->concat(constructorParams->getDeclarations()); }
    IDeclaration getDeclByName(cstring name) const override {
        auto decl = constructorParams->getDeclByName(name);
        if (!decl) decl = typeParameters->getDeclByName(name);
        return decl; }
}

class Type_Parser : Type_ArchBlock, IApply {
    ParameterList applyParams;
    Type_Method getApplyMethodType() const override;
    ParameterList getApplyParameters() const override { return applyParams; }
    toString { return cstring("parser ") + externalName(); }
}

class Type_Control : Type_ArchBlock, IApply {
    ParameterList applyParams;
    Type_Method getApplyMethodType() const override;
    ParameterList getApplyParameters() const override { return applyParams; }
    toString { return cstring("control ") + externalName(); }
}

/// A type referred by name
class Type_Name : Type {
    Path path;
    Type_Name(IR::ID id) : Type(id.srcInfo), path(new IR::Path(id)) {}
    toString{ return path->name; }
    dbprint{ out << path->toString(); }
    const Type* getP4Type() const override { return this; }
    int width_bits() const override {
        BUG("Type_Name is not a canonical type, use getTypeType()?");
        return 0;
    }
}

class Type_Stack : Type_Indexed, Type {
    Type        elementType;
    Expression  size;
    toString{ return elementType->toString() + "[" +
                (sizeKnown() ? Util::toString(getSize()) : "?") + "]"; }
    dbprint{ out << elementType << "[" << size << "]"; }
    bool sizeKnown() const;
    size_t getSize() const override;
    Type at(size_t index) const;
    static const cstring next;
    static const cstring last;
    static const cstring arraySize;
    static const cstring lastIndex;
    static const cstring push_front;
    static const cstring pop_front;
    const Type* getP4Type() const override
    { return new IR::Type_Stack(srcInfo, elementType->getP4Type(), size); }
}

/** Given a declaration
   extern E<T> { ... }
   Type_Specialized represents a type such
   E<bit<32>>
   baseType is Type_Extern E, arguments is a vector containing Type_Bits(32) */
class Type_Specialized : Type {
    Type_Name    baseType;
    Vector<Type> arguments;
    toString{ return baseType->toString() + "<...>"; }
    validate{ arguments->check_null(); }
    const Type* getP4Type() const override;
}

/** Canonical representation of a Type_Specialized;
   only used by the type-checker, never in the IR tree. */
class Type_SpecializedCanonical : Type {
    Type         baseType;     /// canonical baseType; always IMayBeGenericType
    Vector<Type> arguments;    /// canonical type arguments
    /// 'substituted' is baseType with all type
    /// variables substituted with the arguments.
    Type         substituted;  // always IMayBeGenericType
#nodbprint
    validate{
        arguments->check_null();
        BUG_CHECK(baseType->is<IMayBeGenericType>(), "base type %1% is not generic", baseType);
        BUG_CHECK(substituted->is<IMayBeGenericType>(), "substituted %1% is not generic", substituted);
    }
    const Type* getP4Type() const override;
}

/// A declaration that consists of just an identifier, e.g., an enum member
class Declaration_ID : Declaration, CompileTimeValue {
#nodbprint
}

/// The type of a string literal
class Type_String : Type_Base {
#nodbprint
    static Type_String get();
    toString{ return "string"; }
}

class Type_Enum : Type_Declaration, ISimpleNamespace, IAnnotated {
    optional Annotations annotations = Annotations::empty;
    inline IndexedVector<Declaration_ID> members;
    Annotations getAnnotations() const override { return annotations; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return members.getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return members.getDeclaration(name); }
#nodbprint
    validate{ members.check_null(); }
}

/// A member of a serializable enum with a backing value
class SerEnumMember : Declaration, CompileTimeValue {
    Expression value;
    validate { CHECK_NULL(value); }
#nodbprint
}

/** A serializable enumeration with a backing type */
class Type_SerEnum : Type_Declaration, ISimpleNamespace, IAnnotated {
    optional Annotations annotations = Annotations::empty;
    Type type;
    inline IndexedVector<SerEnumMember> members;
    Annotations getAnnotations() const override { return annotations; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return members.getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return members.getDeclaration(name); }
#nodbprint
    validate{ members.check_null(); }
}

class Type_Table : Type, IApply {
    P4Table  table;
    Type_Method getApplyMethodType() const override;
    ParameterList getApplyParameters() const override { return new ParameterList(); }
    /// names for the fields of the struct returned
    /// by applying a table
    static const ID hit;
    static const ID miss;
    static const ID action_run;
    const IR::Type* getP4Type() const override { return nullptr; }
    dbprint { out << table->name; }
}

/// A special enum-like anonymous type that
/// represents all actions in a table's action list.
/// Used for 'switch' statements.
class Type_ActionEnum : Type {
    ActionList actionList;
    bool contains(cstring name) const;
    const IR::Type* getP4Type() const override { return nullptr; }
}

abstract Type_MethodBase : Type, IMayBeGenericType, ISimpleNamespace {
    // we generally want type parameters visited first
    optional TypeParameters typeParameters = new TypeParameters();
    optional NullOK Type returnType = nullptr;
    // nullptr for constructors or functors; nullptr is not void
    ParameterList parameters;

    size_t maxParameterCount() const { return parameters->size(); }
    size_t minParameterCount() const;
    virtual TypeParameters getTypeParameters() const override { return typeParameters; }
    void dbprint(std::ostream& out) const override;
    toString { return "<Method>"; }
    const IR::Type* getP4Type() const override { return nullptr; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return typeParameters->getDeclarations()->concat(parameters->getDeclarations()); }
    IDeclaration getDeclByName(cstring name) const override {
        auto decl = parameters->getDeclByName(name);
        if (!decl) decl = typeParameters->getDeclByName(name);
        return decl; }
}

/// Type for a method or function.
class Type_Method : Type_MethodBase {
#nodbprint
    // The name is only used to give better error messages.
    cstring name;
    toString { return name; }
}

/// Describes an argument of a MethodCall
/// Never used in the program IR; only used by typechecker.
class ArgumentInfo {
    bool leftValue;
    bool compileTimeConstant;
    Type type;
    Argument argument;
    toString { return argument->toString(); }
#nodbprint
}

/// Used to represent the type of a MethodCallExpression
/// for unification.
/// Never appears in the program IR; only used by the typechecker.
class Type_MethodCall : Type {
    Vector<Type>         typeArguments;
    Type_Var             returnType;
    Vector<ArgumentInfo> arguments;
    validate{ typeArguments->check_null(); arguments->check_null(); }
    const Type* getP4Type() const override { return nullptr; }
    toString { return "<Method call>"; }
}

/// Actions look a lot like methods in many respects.
/// However, invoking an action returns another action
/// Having different IR nodes allows performing different transforms in visitors
class Type_Action : Type_MethodBase {
#nodbprint
}

class Method : Declaration, IAnnotated, IFunctional, ISimpleNamespace {
    Type_Method type;
    optional bool isAbstract = false;
    optional Annotations annotations = Annotations::empty;
    size_t maxParameterCount() const { return type->maxParameterCount(); }
    size_t minParameterCount() const { return type->minParameterCount(); }
    void setAbstract() { isAbstract = true; }
    Annotations getAnnotations() const override { return annotations; }
    ParameterList getParameters() const override { return type->parameters; }
    // annotations can refer to parameters, so need to look them up in scope
    IDeclaration getDeclByName(cstring name) const override {
        return type->parameters->getDeclByName(name); }
    Util::Enumerator<IDeclaration> *getDeclarations() const override {
        return type->parameters->getDeclarations(); }
}

class Type_Typedef : Type_Declaration, IAnnotated {
    optional Annotations annotations = Annotations::empty;
    Type                 type;
    int width_bits() const override { return type->width_bits(); }
    Annotations getAnnotations() const override { return annotations; }
#nodbprint
}

/// A newtype is similar to typedef, but it introduces a new type; the
/// the new type does not inherit any
/// of the operations of the original type.  The new type has
/// assignment, equality, and casts to/from the original type.
/// The keyword for newtype is actually `type'.
class Type_Newtype : Type_Declaration, IAnnotated {
    optional Annotations annotations = Annotations::empty;
    Type                 type;
    int width_bits() const override { return type->width_bits(); }
    Annotations getAnnotations() const override { return annotations; }
#nodbprint
}

/// An 'extern' black-box (not a function)
class Type_Extern : Type_Declaration, INestedNamespace, IGeneralNamespace,
                    IMayBeGenericType, IAnnotated {
    optional TypeParameters typeParameters = new TypeParameters;
    optional inline Vector<Method> methods;  // methods can be overloaded, so this is not NameMap
    optional inline NameMap<Attribute, ordered_map> attributes;  // P4_14 only, currently
    optional Annotations annotations = Annotations::empty;

    std::vector<INamespace> getNestedNamespaces() const override { return { typeParameters }; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return attributes.valueEnumerator()->as<const IDeclaration*>()
            ->concat(methods.getEnumerator()->as<const IDeclaration*>()); }
    virtual TypeParameters getTypeParameters() const override { return typeParameters; }
    validate{ methods.check_null(); }
    Annotations getAnnotations() const override { return annotations; }
    /// Returns the method that matches the specified arguments.
    /// Returns nullptr if no method or more than one method match.
    /// In the latter case it also reports an error.
    Method lookupMethod(IR::ID name, Vector<Argument> arguments) const;
    /// Returns the constructor that matches the specified arguments.
    /// Returns nullptr if no constructor or more than one constructor matches.
    /// In the latter case it also reports an error.
    Method lookupConstructor(Vector<Argument> arguments) const
    { return lookupMethod(name, arguments); }
}

/** @} *//* end group irdefs */
